package org.aceor.mddal.sequence;

import com.google.common.base.Throwables;
import org.aceor.mddal.common.exception.BusinessRuntimeException;
import org.aceor.mddal.common.utils.NetUtil;
import org.aceor.mddal.common.zk.DynConfigClientFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Random;

/**
 * Created by lxue on 16/3/20.
 */
public class IdWorker {
    private static final Logger logger = LoggerFactory.getLogger(IdWorker.class);

    private final long workerId;
    private final static long twepoch = 1288834974657L;
    private long sequence = 0L;
    private final static long workerIdBits = 4L;
    public final static long maxWorkerId = -1L ^ -1L << workerIdBits;
    private final static long sequenceBits = 10L;

    private final static long workerIdShift = sequenceBits;
    private final static long timestampLeftShift = sequenceBits + workerIdBits;
    public final static long sequenceMask = -1L ^ -1L << sequenceBits;

    private long lastTimestamp = -1L;

    private static final Random rand = new Random();

    private IdWorker() {
        long temp = 0L;
        String host = NetUtil.getLocalHost();
        try {
            temp = Long.valueOf(DynConfigClientFactory.getConfigClient().getConfig("sequence", host));
        } catch (Exception e) {
            //发生异常默认workId就取0
            logger.warn("IdWorker$not get config from zk, path: /configs/sequence/{}, error info: {}",
                    new Object[]{host, Throwables.getStackTraceAsString(e)});
        } finally {
            if (temp > this.maxWorkerId || temp < 0) {
                throw new IllegalArgumentException(String.format(
                        "worker Id can't be greater than %d or less than 0",
                        this.maxWorkerId));
            }
            workerId = temp;
        }
    }

    public synchronized long nextId() {
        long timestamp = this.timeGen();
        if (this.lastTimestamp == timestamp) {
            this.sequence = (this.sequence + 1) & this.sequenceMask;
            //这个什么时候发生?
            if (this.sequence == 0) {
                timestamp = this.tilNextMillis(this.lastTimestamp);
            }
        } else {
            //为了分库分表散列得更好,这里采用生成随机数0-9
            this.sequence = rand.nextInt(10);
        }
        if (timestamp < this.lastTimestamp) {
            throw new BusinessRuntimeException(
                    String.format(
                            "Clock moved backwards.  Refusing to generate id for %d milliseconds",
                            this.lastTimestamp - timestamp));
        }

        this.lastTimestamp = timestamp;
        long nextId = ((timestamp - twepoch << timestampLeftShift))
                | (this.workerId << this.workerIdShift) | (this.sequence);
        logger.info("IdWorker$generate unique id ==> timestamp:" + timestamp + ",timestampLeftShift:"
                + timestampLeftShift + ",nextId:" + nextId + ",workerId:"
                + workerId + ",sequence:" + sequence);
        return nextId;
    }

    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }


    private static final class IdWorkHolder {
        private static IdWorker idWorker = new IdWorker();
    }

    public static IdWorker getIdWorker() {
        return IdWorkHolder.idWorker;
    }
}
